﻿using System.Reflection;
using System.CodeDom;
using OrganizationName.CodeGeneration;
namespace OrganizationName.Factory
{
    public class Attribute : AnonymousInstance
    {
        public virtual bool NoCreate { get; set; }

        private CodeExpression ToLeftHandSideExpression(ICodeContext codeContext)
        {
            CodeVariableReferenceExpression attributeOwner = new CodeVariableReferenceExpression(codeContext.ParentContext.CodeProvider.UniqueId);

            CodeExpression getAttributeExpression;
            PropertyInfo asProperty = AsProperty(codeContext);
            FieldInfo asField = AsField(codeContext);

            System.Type attributeType = GetConcreteType(codeContext);
            System.Type attributeOwnerConcreteType = GetAttributeOwnerConcreteType(codeContext);
            if (asProperty != null)
            {
                getAttributeExpression = new CodePropertyReferenceExpression(attributeOwner, Name);
            }
            else if (asField != null)
            {
                getAttributeExpression = new CodeFieldReferenceExpression(attributeOwner, Name);
            }
            else
            {
                throw new CodeGenerationException("The attribute cannot be found. Type: " + attributeOwnerConcreteType + ", Attribute: " + Name, codeContext, null);
            }

            return getAttributeExpression;
        }

        public override void InitializationPhase(ICodeContext codeContext)
        {
            CodeExpression getAttributeExpression = ToLeftHandSideExpression(codeContext);

            if (NoCreate)
            {
                codeContext.Statements.Add(new CodeAssignStatement(new CodeVariableReferenceExpression(UniqueId), getAttributeExpression));
            }
            else
            {
                base.InitializationPhase(codeContext);
            }
        }

        public override void FinalizationPhase(ICodeContext codeContext)
        {
            base.FinalizationPhase(codeContext);
            codeContext.Statements.Add(new CodeAssignStatement(ToLeftHandSideExpression(codeContext), new CodeVariableReferenceExpression(UniqueId)));
        }

        public override System.Type GetConcreteType(ICodeContext codeContext)
        {
            if (!string.IsNullOrEmpty(Impl))
            {
                return base.GetConcreteType(codeContext);
            }
            else
            {
                MemberInfo member = AsMember(codeContext);
                PropertyInfo memberAsProperty = member as PropertyInfo;
                FieldInfo memberAsField = member as FieldInfo;
                return memberAsProperty != null ? memberAsProperty.PropertyType : memberAsField.FieldType;
            }
        }

        private bool IsReadOnly(ICodeContext codeContext)
        {
            PropertyInfo asProperty = AsProperty(codeContext);
            return asProperty != null && !asProperty.CanWrite;
        }

        private System.Type GetAttributeOwnerConcreteType(ICodeContext codeContext)
        {
            return (codeContext.ParentContext.CodeProvider as GenericMaterial).GetConcreteType(codeContext.ParentContext);
        }

        public MemberInfo AsMember(ICodeContext codeContext)
        {
            System.Type attributeOwnerType = GetAttributeOwnerConcreteType(codeContext);
            MemberInfo[] members = attributeOwnerType.GetMember(Name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.Instance);
            if (members.Length == 0)
            {
                throw new CodeGenerationException("Attribute not found: " + Name, codeContext, null);
            }
            return members[0];
        }

        public PropertyInfo AsProperty(ICodeContext codeContext)
        {
            return AsMember(codeContext) as PropertyInfo;
        }

        public FieldInfo AsField(ICodeContext codeContext)
        {
            return AsMember(codeContext) as FieldInfo;
        }

    }
}
